/*
 * Copyright 2006-2011 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 *
 * https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
 * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations under the License.
 */
package org.springframework.security.jwt;

import static org.junit.Assert.assertEquals;
import static org.springframework.security.jwt.codec.Codecs.utf8Encode;

import org.jruby.embed.ScriptingContainer;
import org.junit.Assume;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.MethodRule;
import org.junit.runners.model.FrameworkMethod;
import org.junit.runners.model.Statement;
import org.springframework.security.jwt.crypto.sign.MacSigner;

/**
 * Tests for compatibility with Ruby's JWT Gem.
 *
 * Requires a local JRuby installation with jruby.home as a system property. Using RVM
 * sets this up automatically (e.g. "rvm use jruby-1.7.12"), or alternatively Maven can be
 * run with:
 * 
 * <pre>
 * mvn -DargLine="-Djruby.home=${JRUBY_HOME}" test
 * </pre>
 *
 * See https://github.com/jruby/jruby/wiki/JavaIntegration for issues with gem loading
 *
 * You also need to: jruby -S gem install jwt jruby -S gem install jruby-openssl
 *
 * for the tests to work. If the environment isn't set up correctly or jruby.home isn't
 * set, they will be skipped.
 *
 * @author Luke Taylor
 */
public class RubyJwtIntegrationTests {

	public static final String TEST_CLAIMS = "{\"some\":\"payload\"}";

	MacSigner hmac = new MacSigner(utf8Encode("secret"));

	@Rule
	public final MethodRule jrubySetupOk = new JRubyJwtInstalled();

	@BeforeClass
	public static void setUp() throws Exception {
		// System.setProperty("jruby.home", "/Users/luke/Work/tools/jruby-1.6.5.1");
		// System.setProperty("jruby.lib", "/Users/luke/Work/tools/jruby-1.6.5.1/lib");
	}

	@Test
	public void rubyCanDecodeHmacSignedToken() throws Exception {
		Jwt jwt = JwtHelper.encode(TEST_CLAIMS, hmac);
		ScriptingContainer container = new ScriptingContainer();
		container.put("@token", jwt.getEncoded());
		container.put("@claims", "");
		String script = "require \"jwt\"\n"
				+ "@claims = JWT.decode(@token, \"secret\", \"HS256\")[0].to_json\n"
				+ "puts @claims";
		container.runScriptlet(script);
		assertEquals(TEST_CLAIMS, container.get("@claims"));
	}

	@Test
	public void canDecodeRubyHmacSignedToken() throws Exception {
		ScriptingContainer container = new ScriptingContainer();
		container.put("@token", "xxx");
		String script = "require \"jwt\"\n"
				+ "@token = JWT.encode({\"some\" => \"payload\"}, \"secret\", \"HS256\")\n"
				+ "puts @token";
		container.runScriptlet(script);
		String token = (String) container.get("@token");
		Jwt jwt = JwtHelper.decodeAndVerify(token, hmac);
		assertEquals(TEST_CLAIMS, jwt.getClaims());
		container.terminate();
	}

	public static class JRubyJwtInstalled implements MethodRule {
		private static boolean setupOkSet;
		private static boolean setupOk;

		public JRubyJwtInstalled() {
			if (!setupOkSet) {
				setupOk = false;
				setupOkSet = true;
				try {
					String script = "require \"jwt\"\n"
							+ "require \"bouncy-castle-java\"\n" + "require \"openssl\"";
					ScriptingContainer container = new ScriptingContainer();
					container.runScriptlet(script);
					setupOk = true;
				}
				catch (Exception e) {
					System.out.println(
							"jruby.home not set or JWT gem not available. JWT ruby integration tests will be skipped"
									+ e.getMessage());
				}
			}
		}

		@Override
		public Statement apply(final Statement base, FrameworkMethod method,
				Object target) {
			return new Statement() {
				@Override
				public void evaluate() throws Throwable {
					Assume.assumeTrue(setupOk);
					base.evaluate();
				}
			};
		}
	}

}
